using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Web;

using Framework;

namespace SchemaTrading
{
    //Collection Class (Customisable half)
    public partial class CDataList
    {
        #region Filters
        #endregion

        #region Searching (Optional)
        //Represents a simple search box to search PK and any string columns (add overloads as required, based on the pattern below)
        //e.g. public CDataList Search(string nameOrId, int seriesId) { ...
        public CDataList Search(string nameOrId)
        {
            //1. Normalisation
            nameOrId = (nameOrId??string.Empty).Trim().ToLower();
            
            //2. Start with a complete list
            CDataList results = this;
            
            //3. Use any available index, such as those generated for fk/bool columns
            //Normal Case - non-unique index (e.g. foreign key)
            //if (int.MinValue != seriesId) results = results.GetBySeriesId(seriesId);

            //Special case - unique index (e.g. primary key)
            /*
            if (!string.IsNullOrEmpty(nameOrId)) 
            {
                int id;
                if (int.TryParse(nameOrId, out id))
                {
                    CData obj = this.GetById(id);
                    if (null != obj)
                    {
                        results = new CDataList(1);
                        results.Add(obj);
                        return results;
                    }
                }
            }
            */
            
            //4. Exit early if remaining (non-index) filters are blank
            if (string.IsNullOrEmpty(nameOrId)) return results; 
            
            //5. Manually search each record using custom match logic, building a shortlist
            CDataList shortList = new CDataList();
            foreach (CData i in results)
                if (Match(nameOrId, i))
                    shortList.Add(i); 
            return shortList;
        }
        //Manual Searching e.g for string-based columns i.e. anything not indexed (add more params if required)
        private bool Match(string name, CData obj)
        {
            if (!string.IsNullOrEmpty(name)) //Match any string column
            {
                return false;   //If filter is active, reject any items that dont match
            }
            return true;    //No active filters (should catch this in step #4)
        }
        #endregion

        #region Cloning
        public CDataList Clone(CDataSrc target) //, int parentId)
        {
            //No Transaction
            if (target is CDataSrcRemote)
                return Clone(target, null); //, parentId);

            //Transaction
            using (IDbConnection cn = target.Local.Connection())
            {
                IDbTransaction tx = cn.BeginTransaction();
                try
                {
                    CDataList clone = Clone(target, tx); //, parentId);
                    tx.Commit();
                    return clone;
                }
                catch
                {
                    tx.Rollback();
                    throw;
                }
            }
        }
        public CDataList Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            CDataList list = new CDataList(this.Count);
            foreach (CData i in this)
                list.Add(i.Clone(target, txOrNull)); //, parentId));  *Child entities must reference the new parent
            return list;
        }
        #endregion
        
        #region Export to Csv
        //Web - Need to add a project reference to System.Web, or comment out these two methods
        public void ExportToCsv(HttpResponse response, string fileName, List<EData> columns, List<EDecision> decisions)
        {
            CDataSrc.ExportToCsv(response, fileName); //Standard response headers
            StreamWriter sw = new StreamWriter(response.OutputStream);
            ExportToCsv(sw, columns, decisions);
            sw.Flush();
            response.End();
        }

        //Non-web
        public void ExportToCsv(string filePath, List<EData> columns, List<EDecision> decisions)
        {
            StreamWriter sw = new StreamWriter(filePath);
            ExportToCsv(sw, columns, decisions);
            sw.Close();
        }

        //Logic
        protected void ExportToCsv(StreamWriter sw, List<EData> columns, List<EDecision> decisions)
        {
            List<string> headings = new List<string>(1 + columns.Count + decisions.Count);
            headings.Add("Date");
            foreach (EData i in columns)
                headings.Add(i.ToString());
            foreach (EDecision i in decisions)
                headings.Add(string.Concat(i, " (", CData.GetTooltip(i), ")"));

            CDataSrc.ExportToCsv(headings.ToArray(), sw);
            foreach (CData d in this)
            {
                List<object> data = new List<object>(1 + columns.Count + decisions.Count);
                data.Add(d.DataDate.ToString("d-MMM-yyyy"));
                foreach (EData i in columns)
                    data.Add(d.GetData(i));
                foreach (EDecision i in decisions)
                    data.Add(d.GetDecision(i));
                CDataSrc.ExportToCsv(data.ToArray(), sw);
            }
        }
        #endregion

        #region Custom
        public DateTime MinDate { get { return Count == 0 ? DateTime.MinValue : this[0].DataDate; } }
        public DateTime MaxDate { get { return Count == 0 ? DateTime.MinValue : this[Count - 1].DataDate; } }
        public TimeSpan TimeSpan { get { return Count == 0 ? TimeSpan.MinValue : MaxDate.AddDays(1).Subtract(MinDate); } }
        public int TotalDays { get { return (int)TimeSpan.TotalDays; } }
        #endregion



        #region Foreign-Key Indices (Subsets)
        //Index by DataSeriesId
        public CDataList GetBySeriesId(int seriesId)
        {
            CDataList temp = null;
            if (!IndexBySeriesId.TryGetValue(seriesId, out temp))
            {
                temp = new CDataList();
                IndexBySeriesId[seriesId] = temp;
            }
            return temp;
        }

        [NonSerialized]
        private Dictionary<int, CDataList> _indexBySeriesId;
        private Dictionary<int, CDataList> IndexBySeriesId
        {
            get
            {
                if (null == _indexBySeriesId)
                {
                    Dictionary<int, CDataList> index = new Dictionary<int, CDataList>();
                    CDataList temp = null;
                    foreach (CData i in this)
                    {
                        if (!index.TryGetValue(i.DataSeriesId, out temp))
                        {
                            temp = new CDataList();
                            index[i.DataSeriesId] = temp;
                        }
                        temp.Add(i);
                    }
                    _indexBySeriesId = index;
                    foreach (CDataList i in index.Values)
                        i.DoCalcs();
                }
                return _indexBySeriesId;
            }
        }
        #endregion

        #region Date Index 
        public CData GetByDate(DateTime date)
        {
            CData c = null;
            IndexByDate.TryGetValue(date, out c);
            return c;
        }
        [NonSerialized]
        private Dictionary<DateTime, CData> _indexByDate;
        private Dictionary<DateTime, CData> IndexByDate
        {
            get
            {
                if (null != _indexByDate && _indexByDate.Count == this.Count)
                    return _indexByDate;

                _indexByDate = new Dictionary<DateTime, CData>(this.Count);
                foreach (CData i in this)
                    _indexByDate[i.DataDate] = i;
                return _indexByDate;
            }
        }
        #endregion


        #region Trading Simulation
        public void DoCalcs()
        {
            lock (this)
            {
                foreach (CData i in this)
                {
                    i.ClearAllCalcs();
                    i.Prev = Prev(i);
                    i.Next = Next(i);
                    if (null == i.Prev)
                        i.Simulation = new CSimulation(i);
                    else
                        i.Simulation = new CSimulation(i.Prev.Simulation, i);
                }
            }
        }

        public CData Last { get { return 0 == Count ? null : this[this.Count - 1]; } }
        public CData Prev(CData d)
        {
            int index = this.IndexOf(d);
            if (-1 == index)
                index = this.IndexOf(this.GetById(d.DataId));
            if (0 == index)
                return null;
            return this[index - 1];
        }
        public CData Next(CData d)
        {
            int index = this.IndexOf(d);
            if (-1 == index)
                index = this.IndexOf(this.GetById(d.DataId));
            if (this.Count - 1 == index)
                return null;
            return this[index + 1];
        }

        public double SimulatedTotal { get { return null == Last ? CSimulation.LEVERAGE * CSimulation.START : this.Last.SimulatedTotal; } }
        public double SimulatedProfit { get { return SimulatedTotal - CSimulation.LEVERAGE * CSimulation.START; } }
        public double SimulatedReturns { get { return SimulatedProfit / CSimulation.START; } }
        public double AnnualisedReturns { get { return Math.Pow(1 + SimulatedReturns, 365.0 / TotalDays) - 1; } }
        public double ExposureAdjustedReturns { get { return AnnualisedReturns / (AverageExposure * CSimulation.LEVERAGE); } }
        private double _avgExp = double.NaN;
        public double AverageExposure
        {
            get
            {
                if (double.IsNaN(_avgExp))
                {
                    double total = 0;
                    foreach (CData i in this)
                        total += i.Simulation.Exposure;
                    _avgExp =  total / Count;
                }
                return _avgExp;
            }
        }
        private double _maxExp = double.NaN;
        public double MaxExposure
        {
            get
            {
                if (double.IsNaN(_maxExp))
                {
                    double max = 0;
                    foreach (CData i in this)
                        max = Math.Max(max, i.Simulation.Exposure);
                    _maxExp = max;
                }
                return _maxExp;
            }
        }
        private double _sumClosing = double.NaN;
        public double SumClosing
        {
            get
            {
                if (double.IsNaN(_sumClosing))
                {
                    double sum = 0;
                    foreach (CData i in this)
                        sum += i.DataClose;
                    _sumClosing = sum;
                }
                return _sumClosing;
            }
        }
        private double _sumSquaresClosing = double.NaN;
        public double SumSquaresClosing
        {
            get
            {
                if (double.IsNaN(_sumSquaresClosing))
                {
                    double sum = 0;
                    foreach (CData i in this)
                        sum += Math.Pow(i.DataClose, 2);
                    _sumSquaresClosing = sum;
                }
                return _sumSquaresClosing;
            }
        }
        public double MeanClosing { get { return SumClosing / Count; } }
        public double VolatilityClosing { get { return (SumSquaresClosing / Count) - Math.Pow(MeanClosing, 2); } }
        public double StdDevClosing { get { return Math.Sqrt(VolatilityClosing); } }

        private int _wins   = int.MinValue;
        private int _losses = int.MinValue;
        private int _flat   = int.MinValue;
        public int Wins     { get { if (int.MinValue == _wins)  DoCount(); return _wins;    } }
        public int Losses   { get { if (int.MinValue == _losses)DoCount(); return _losses;  } }
        public int Flat { get { if (int.MinValue == _flat)  DoCount(); return _flat; } }
        public double WinsPercent { get { return (double)Wins / (double)Count; } }
        public double LossesPercent { get { return (double)Losses / (double)Count; } }
        public double FlatPercent { get { return (double)Flat / (double)Count; } }
        private void DoCount()
        {
            if (this.Count == 0)
                return;
            lock (this)
            {
                _wins = 0;
                _losses = 0;
                _flat = 0;
                foreach (CData i in this)
                    if (double.IsNaN(i.Returns) || 0 == i.Returns)
                        _flat += 1;
                    else if (i.Returns > 0)
                        _wins += 1;
                    else
                        _losses += 1;
            }
        }

        private double _low = double.NaN;
        private double _high = double.NaN;
        public double Low  { get { if (double.IsNaN(_low))  DoHighLow(); return _low; } }
        public double High { get { if (double.IsNaN(_high)) DoHighLow(); return _high; } } 
        private void DoHighLow()
        {
            if (this.Count == 0)
                return;
            lock (this)
            {
                _low = double.PositiveInfinity;
                _high = 0;
                foreach (CData i in this)
                    if (_low > i.SimulatedTotal)
                        _low = i.SimulatedTotal;
                    else if (_high < i.SimulatedTotal)
                        _high = i.SimulatedTotal;
            }
        }
        #endregion


    }
}
